<?php
namespace Tbf\Bundle\TestBundle\TestSuite;
use Symfony\Component\Yaml\Yaml;
use Tbf\Bundle\TbfBundle\DependencyInjection\TbfContainerAware;
use Tbf\Component\Arrays\ArrayFunction;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\DependencyInjection\Container;

/**
 * TODO: 分离各种功能,增加聚合度,这个类仅仅提供一些方便
 */
class TbfTestCase extends \PHPUnit_Framework_TestCase
{
    protected static $kernel;
    protected $client;
    /**
     * 获取测试的kernel
     * @return Kernel
     */
    public static function getKernel()
    {
        if (self::$kernel===null) {
            self::$kernel = self::newKernel();
        }

        return self::$kernel;
    }
    public static function newKernel()
    {
        $kernel = new \AppKernel('test',true);
        $kernel->boot();

        return $kernel;
    }

    /**
     * @return \Symfony\Component\DependencyInjection\ContainerInterface
     */
    public function getClientDic()
    {
        if ($this->client===null) {
            $this->client = $this->newKernel()->getContainer();
        }

        return $this->client;
    }
    /**
     * @return TbfContainerAware
     */
    public function getClientTbfContainerAware()
    {
        $obj = new TbfContainerAware();
        $obj->setContainer($this->getClientDic());
        return $obj;
    }
    /**
     * @return Container
     */
    public function getContainer()
    {
        return $this->getKernel()->getContainer();
    }
    public function get($name)
    {
        return $this->getContainer()->get($name);
    }
    public function getParameter($name)
    {
        return $this->getContainer()->getParameter($name);
    }
    public function has($name)
    {
        return $this->getContainer()->has($name);
    }

    public function setUp()
    {
        $this->setUpDatabase();
        $this->client = null;
        parent::setUp();
    }
    /**
    * 确认相等
    * 1.如果$expect和$input都是某种数值(is_numeric),则用==比较相等
    * 2.如果$expect和$input都是数组,则用PHPUnit_Framework_TestCase::assertEquals比较相等
    * 3.如果$expect和$input都是对象,则用PHPUnit_Framework_TestCase::assertEquals比较相等
    * 3.其他所有情况,均使用PHPUnit_Framework_TestCase::assertSame比较相等
    * 和PHPUnit_Framework_TestCase::assertEquals最大的不同是'abc',true 不再能通过了...
    */
    public static function assertEquals($expected,$actual,$message = '',$delta = 0,
        $maxDepth = 10,$canonicalize = FALSE,$ignoreCase = FALSE){
        Assert::assertEquals($expected,$actual,$message,$delta,$maxDepth,$canonicalize,$ignoreCase);
    }

    /**
     * @return \Doctrine\Dbal\Connection
     */
    public function getDbalConnection()
    {
        return $this->get('doctrine')->getConnection();
    }
    public function getTableData($table_name,$rekey_field='id')
    {
        return $this->get('tbf_database')->getTableData($table_name,$rekey_field);
    }
    /**
     * 设置数据库
     * */
    public function setDbData($data)
    {
        $this->getContainer()->get('tbf_database.set_table_data')->setTableData($data);
    }

    /**
     * 使用yaml设置数据库
     * @param $yaml
     */
    public function setTableDataYaml($yaml){
        $this->setDbData(Yaml::parse($yaml));
    }

    public function assertHtml($string)
    {
        Assert::assertHtml($string);
    }


    public function assertColumnSubMatrix($origin,$sub)
    {
        $ret = ArrayFunction::checkColumnSubMatrix($origin,$sub);
        $this->assertTrue($ret);
    }
    public function assertSubArray($sub,$origin)
    {
        $ret = ArrayFunction::checkSubArray($origin,$sub);
        if ($ret !== true) {
            $this->fail(
                'sub:' . var_export($sub,true) . "\n" .
                'origin:' . var_export($origin,true));
        }
        $this->assertTrue($ret);
    }

    /**
     * 数组无顺序相等
     * 1.忽略第一维的下标
     * 2.数组的数据必须完全相等(使用assertEquals)
     * 3.忽略数组顺序
     * @param $expect
     * @param $origin
     * @param string $msg
     */
    public function assertArrayUnOrderEquals($expect,$origin,$msg='')
    {
        $this->assertArrayUnOrderSubMatrix($expect,$origin,$msg);
        $this->assertArrayUnOrderSubMatrix($origin,$expect,$msg);
    }

    /**
     * 数组无顺序相等,
     * 并且第2维可以缺少某些字段,
     * @param $bigger
     * @param $smaller
     * @param string $msg
     */
    public function assertArrayUnOrderSubMatrix($small,$big,$msg='')
    {
        if (!is_array($small)) {
            $this->fail('small type is not array'.$msg);
        }
        if (!is_array($big)) {
            $this->fail('big type is not array'.$msg);
        }
        if (count($small)!==count($big)) {
            $this->fail('array element number not equals'.$msg);
        }
        foreach ($small as $small_row) {
            $small_found = false;
            foreach ($big as $big_key=>$big_row) {
                if (!$this->checkSubVector($small_row,$big_row)) {
                    continue;
                }
                $small_found = true;
                unset($big_row[$big_key]);
                break;
            }
            if ($small_found===false) {
                $this->fail('array no equal'.$msg);
            }
        }
        $this->assertTrue(true);
    }
    protected function checkSubVector($small,$big)
    {
        foreach ($small as $field_name=>$value) {
            if (!array_key_exists($field_name,$big)) {
                return false;
            }
            if (!$this->isNumEquals($big[$field_name],$small[$field_name])) {
                return false;
            }
        }

        return true;
    }
    protected function isNumEquals($a,$b)
    {
        if (is_int($a) ||
            is_int($b)){
            if ($a==$b) {
                return true;
            }
        }
        if ($a===$b) {
            return true;
        }

        return false;
    }
    /**
     * ajax发送消息
     */
    public function ajax($origin_data)
    {
        static $ajax = null;
        if (null === $ajax) {
            $ajax = $this->get('tbf_test.ajax');
            if (empty($ajax)) {
                $this->markTestSkipped('want test ajax without config host');
            }
        }

        return $ajax->request($origin_data);
    }
    public function resetAjax(){
        $this->get('tbf_test.ajax')->reset();
    }
    /**
    * ...不管用....这个函数自身没有详细测试....有无法解决的小bug
      在新进程里面进行测试
      在新进程中执行回调
      回调的第一个参数是一个assert实例，
      可以将新进程里面的测试结果返回给当前进程
      新进程执行回调后自动退出
      @param Closure $cb
     *  @deprecated
     */
    public function newProcess($cb)
    {
        $_this = $this;

        return ProcessUtil::runPhpInFork(
            function () use ($cb,$_this) {
                return $cb($_this);
            });
    }
    /**
     * @return \Tbf\Database\Model\ThinkPhpModel
     * @deprecated
     */
    public function getDb()
    {
        return $this->get('tbf_database.tp');
    }
    /**
     * 全局调用次数函数,
     * 返回某个name被调用的次数,(包括本次)
     * 测试样例版
     * @deprecated
     * */
    public function getCallNum($name)
    {
        static $num = array();
        if (empty($num[$name])) {
            $num[$name] = 0;
        }
        $num[$name] += 1;

        return $num[$name];
    }
    /**
     * 本类开始测试的时候运行一次
     *  @deprecated
     * */
    public function setUpOnce()
    {
    }
    /**
     * 数据库测试用mock数据,
     * array()表示这个测试不mock数据库
     * @deprecated
     * */
    public function getDbArray()
    {
        return array();
    }
    /**
     * 默认获取当前表的所有数据
     * !注意,如果主键名不是id,需自行重载这个函数!
     * @deprecated
     * */
    public function getThisData()
    {
        if (isset($this->table_name)) {
            return $this->getTableData($this->table_name);
        }
        throw new TestSuiteException(
            sprintf('need set $this->table_name of "%s" ',get_class($this)));
    }
    /**
     * @deprecated
     */
    private function setUpDatabase()
    {
        //不想测试数据库
        $dbArray = $this->getDbArray();
        if (empty($dbArray)) {
            return;
        }
        $this->setDbData($dbArray);
    }
}
